package server;

import java.io.UnsupportedEncodingException;
import java.net.SocketAddress;
import java.nio.ByteBuffer;

public class Message {

	// SHA-1 is 40 bytes
	private static final int PUBID_LEN = 40;
	// 1 byte type, PUBID_LEN, 8 byte seq #, 1 byte flags
	private static final int HEADER_SIZE = 1 + PUBID_LEN + 8 + 1;

	public static enum MsgType {
		SUB, PUB, UNSUB, REQUEST, HERE, KEEPALIVE, PING, SURVEY, LOAD, CONNECT;

		public byte getVal() {
			switch (this) {
			case UNSUB:
				return 0x02;
			case PUB:
				return 0x01;
			case SUB:
				return 0x00;
			case REQUEST:
				return 0x03;
			case HERE:
				return 0x04;
			case KEEPALIVE:
				return 0x05;
			case PING:
				return 0x06;
			case SURVEY:
				return 0x07;
			case LOAD:
				return 0x08;
			case CONNECT:
				return 0x09;
			default:
				return 0x00;
			}
		}

		public static MsgType getType(byte b) {
			switch (b) {
			case 0x02:
				return UNSUB;
			case 0x01:
				return PUB;
			case 0x03:
				return REQUEST;
			case 0x04:
				return HERE;
			case 0x05:
				return KEEPALIVE;
			case 0x06:
				return PING;
			case 0x07:
				return SURVEY;
			case 0x08:
				return LOAD;
			case 0x09:
				return CONNECT;
			default:
				return SUB;
			}
		}
	}

	public MsgType type;
	public String pubId;
	public long seq;
	public byte flags;
	public String data;
	public byte[] rawData;
	
	public SocketAddress addr;

	public Message(MsgType type, String pubId, long seq, byte[] data) {
		this.type = type;
		this.pubId = pubId;
		this.seq = seq;
		this.flags = 0x0;
		this.rawData = data;
	}

	public Message(MsgType type, String pubId, long seq, String data)
			throws UnsupportedEncodingException {
		this(type, pubId, seq, data.getBytes(Server.ENCODING));
		this.data = data;
	}

	public void setData(String data) throws UnsupportedEncodingException {
		this.rawData = data.getBytes(Server.ENCODING);
		this.data = data;
	}
	
	/**
	 * Returns a FLIPPED ByteBuffer of the serialized <tt>Message</tt>.
	 * 
	 * @return ByteBuffer (with <tt>flip()</tt> called) of this <tt>Message</tt>
	 *         , or null if encoding isn't supported.
	 */
	public ByteBuffer serialize() {
		try {
			ByteBuffer buf = ByteBuffer.allocate(HEADER_SIZE + rawData.length);
			buf.put(type.getVal());
			buf.put(pubId.getBytes(Server.ENCODING));
			buf.putLong(seq);
			buf.put(flags);
			buf.put(rawData);
			buf.flip();

			return buf;
		} catch (UnsupportedEncodingException e) {
			return null;
		}
	}

	public static Message deserialize(ByteBuffer buf) {
		MsgType type = MsgType.getType(buf.get(0));
		try {
			String pubId = new String(buf.array(), 1, PUBID_LEN,
					Server.ENCODING);
			long seq = buf.getLong(1 + PUBID_LEN);
			byte flags = buf.get(1 + PUBID_LEN + 8);
			int strLen = buf.limit() - HEADER_SIZE;
			byte[] raw = new byte[strLen];
			System.arraycopy(buf.array(), HEADER_SIZE, raw, 0, strLen);
			String data = new String(raw, 0, strLen, Server.ENCODING);

			Message m = new Message(type, pubId, seq, data);
			m.flags = flags;
			m.rawData = new byte[strLen];
			System.arraycopy(raw, 0, m.rawData, 0, strLen);

			return m;
		} catch (UnsupportedEncodingException e) {
			return null;
		}
	}

	@Override
	public String toString() {
		return "{type: " + type + ", seq: " + seq + ", pubid: " + pubId
				+ ", data: " + data + "}";
	}
}
